package come.example.infrastructure.persistent.repository;

/*
策略仓储实现
 */

import come.example.domain.strategy.model.entity.StrategyAwardEntity;
import come.example.domain.strategy.model.entity.StrategyEntity;
import come.example.domain.strategy.model.entity.StrategyRuleEntity;
import come.example.domain.strategy.model.vo.*;
import come.example.domain.strategy.repository.IStrategyRepository;
import come.example.infrastructure.persistent.dao.*;
import come.example.infrastructure.persistent.po.*;
import come.example.infrastructure.persistent.redis.IRedisService;
import come.example.types.common.Constants;
import come.example.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingDeque;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static come.example.types.enums.ResponseCode.UN_ASSEMBLED_STRATEGY_ARMORY;


@Slf4j
@Repository
public class StrategyRepository implements IStrategyRepository {

    private static final Logger logger = LoggerFactory.getLogger(StrategyRepository.class);

    @Resource
    private IStrategyDao strategyDao;

    @Resource
    private IStrategyRuleDao strategyRuleDao;

    @Resource
    private IStrategyAwardDao strategyAwardDao;

    @Resource
    private IRedisService redisService;

    @Resource
    private IRuleTreeDao ruleTreeDao;

    @Resource
    private IRuleTreeNodeDao ruleTreeNodeDao;

    @Resource
    private IRuleTreeNodeLineDao ruleTreeNodeLineDao;



    @Override
    public List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId){
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_LIST_KEY + strategyId;
        //从 Redis 缓存中获取数据
        List<StrategyAwardEntity> strategyAwardEntities = redisService.getValue(cacheKey);
        if(null != strategyAwardEntities && !strategyAwardEntities.isEmpty()){
            return strategyAwardEntities;
        }
        //如果是空就去库里读取数据
        List<StrategyAward> strategyAwards = strategyAwardDao.queryStrategyAwardListByStrategyId(strategyId);
        strategyAwardEntities = new ArrayList<>(strategyAwards.size());
        /*
        将从数据库中查询到的 StrategyAward 对象转换为 StrategyAwardEntity 对象，并将其存储在一个列表中
         */
        for(StrategyAward strategyAward : strategyAwards) {
            /*
            使用建造者模式创建一个新的 StrategyAwardEntity 对象。每个 strategyAward 中的属性
            （如 strategyId、awardId、awardCount 等）被提取并设置到新的实体对象中。
             */
            StrategyAwardEntity strategyAwardEntity = StrategyAwardEntity.builder()
                        .strategyId(strategyAward.getStrategyId())
                        .awardId(strategyAward.getAwardId())
                        .awardCount(strategyAward.getAwardCount())
                        .awardCountSurplus(strategyAward.getAwardCountSurplus())
                        .awardRate(strategyAward.getAwardRate())
                        .sort(strategyAward.getSort())
                        .awardTitle(strategyAward.getAwardTitle())
                        .awardSubtitle(strategyAward.getAwardSubtitle())
                        .build();
            //将构建好的 StrategyAwardEntity 对象添加到 strategyAwardEntities 列表中
            strategyAwardEntities.add(strategyAwardEntity);
        }
        redisService.setValue(cacheKey, strategyAwardEntities);
        return strategyAwardEntities;
    }

    @Override
    public void storeStrategyAwardSearchTables(String key, Integer rateRange, Map<Integer, Integer> strategyAwardSearchTables) {
        //调redis存过来
        //1.存储抽奖策略范围值，如10000，用于生成1000以内的随机数
        // 存键和值
        redisService.setValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key, rateRange);
        //2.存储概率查找表
        Map<Integer, Integer> cacheRateTable = redisService.getMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key);
        cacheRateTable.putAll(strategyAwardSearchTables);
    }

    /*
    从 Redis 中获取对应的概率范围值。
    这两个多态相当于功能的扩展
     */
    @Override
    public int getRateRange(Long strategyId) {
        //存的是int-value，直接返回即可
        if(strategyId == null){
            throw new IllegalArgumentException("strategyId cannot be null");
        }
        return getRateRange(String.valueOf(strategyId));
    }

    @Override
    public int getRateRange(String key) {
        //存的是int-value，直接返回即可
        if(key == null){
            throw new IllegalArgumentException("key cannot be null");
        }
        if (redisService == null) {
            throw new IllegalStateException("redisService has not been injected");
        }
        String cacheKey = Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key;
        // 防止没有装配成功 在通过缓存获取抽奖范围值时，如果忘记初始化策略到缓存中会抛异常。所以新增加了判断代码，增强健壮性。
        if(!redisService.isExists(cacheKey)){
            throw new AppException(UN_ASSEMBLED_STRATEGY_ARMORY.getCode(), cacheKey + Constants.COLON + UN_ASSEMBLED_STRATEGY_ARMORY.getInfo());
        }
        return redisService.getValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key);
    }

    // 第一个是哈希数据结构的键，第二个是要获取值对应的字段名（也就是 rateKey）
    @Override
    public Integer getStrategyAwardAssemble(String key, Integer rateKey) {
        return redisService.getFromMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key, rateKey);
    }

    //优化查询：先在缓存中查询，如果没有再去数据库中获取数据
    //在constants.java中
    @Override
    public StrategyEntity queryStrategyEntityByStrategyId(Long strategyId) {
        // 检查 strategyId 是否为 null
        if (strategyId == null) {
            throw new IllegalArgumentException("strategyId cannot be null");
        }
        //优先从缓存中获取
        String cacheKey = Constants.RedisKey.STRATEGY_KEY + strategyId;
        StrategyEntity strategyEntity = redisService.getValue(cacheKey);
        if(null != strategyEntity) return strategyEntity;
        logger.info("StrategyId: " + strategyId);
        Strategy strategy = strategyDao.queryStrategyByStrategyId(strategyId);
        strategyEntity = StrategyEntity.builder()
                .strategyId(strategy.getStrategyId())
                .strategyDesc(strategy.getStrategyDesc())
                .ruleModels(strategy.getRuleModels())
                .build();
        //先装到缓存里再return
        redisService.setValue(cacheKey, strategyEntity);
        return strategyEntity;
    }

    @Override
    public StrategyRuleEntity queryStrategyRule(Long strategyId, String ruleModel) {
        StrategyRule strategyRuleReq = new StrategyRule();
        strategyRuleReq.setStrategyId(strategyId);
        strategyRuleReq.setRuleModel(ruleModel);
        log.info("strategyRuleReq:= {}" , strategyRuleReq.toString());
        StrategyRule strategyRuleRes = strategyRuleDao.queryStrategyRule(strategyRuleReq);
        log.info("strateguRuleRes: {}" , strategyRuleRes.toString());

        return StrategyRuleEntity.builder()
                .strategyId(strategyRuleRes.getStrategyId())
                .awardId(strategyRuleRes.getAwardId())
                .ruleType(strategyRuleRes.getRuleType())
                .ruleModel(strategyRuleRes.getRuleModel())
                .ruleValue(strategyRuleRes.getRuleValue())
                .ruleDesc(strategyRuleRes.getRuleDesc())
                .build();
    }

    @Override
    public String queryStrategyRuleValue(Long strategyId, String ruleModel) {
        return queryStrategyRuleValue(strategyId, null, ruleModel);
    }

    @Override
    public String queryStrategyRuleValue(Long strategyId, Integer awardId, String ruleModel) {
        //查询数据库的信息
        StrategyRule strategyRule = new StrategyRule();
        strategyRule.setStrategyId(strategyId);
        strategyRule.setAwardId(awardId);
        strategyRule.setRuleModel(ruleModel);
        return strategyRuleDao.queryStrategyRuleValue(strategyRule);
    }

    // 简单的查询
    @Override
    public StrategyAwardRuleModelVO queryStrategyAwardRuleModel(Long strategyId, Integer awardId) {
        StrategyAward strategyAward = new StrategyAward();
        strategyAward.setStrategyId(strategyId);
        strategyAward.setAwardId(awardId);
        // 查对应的ruleModel
        String ruleModels = strategyAwardDao.queryStrategyAwardRuleModels(strategyAward);
        return StrategyAwardRuleModelVO.builder().ruleModels(ruleModels).build();

    }

    // 组装要的数据
    @Override
    public RuleTreeVO queryRuleTreeVOByTreeId(String treeId) {
        // 优先从缓存中获取
        String cacheKey = Constants.RedisKey.RULE_TREE_VO_KEY + treeId;
        RuleTreeVO ruleTreeVOCache = redisService.getValue(cacheKey);
        if(null != ruleTreeVOCache) return ruleTreeVOCache;

        // 优先从数据库中获取
        RuleTree ruleTree = ruleTreeDao.queryRuleTreeByTreeId(treeId);
        List<RuleTreeNode> ruleTreeNodes = ruleTreeNodeDao.queryRuleTreeNodeListByTreeId(treeId);
        List<RuleTreeNodeLine> ruleTreeNodeLines = ruleTreeNodeLineDao.queryRuleTreeNodeLineListByTreeId(treeId);

        // 1. tree node line 转换Map结构
        Map<String, List<RuleTreeNodeLineVO>> ruleTreeNodeLineMap = new HashMap<>();
        for (RuleTreeNodeLine ruleTreeNodeLine : ruleTreeNodeLines) {
            RuleTreeNodeLineVO ruleTreeNodeLineVO = RuleTreeNodeLineVO.builder()
                    .treeId(ruleTreeNodeLine.getTreeId())
                    .ruleNodeFrom(ruleTreeNodeLine.getRuleNodeFrom())
                    .ruleNodeTo(ruleTreeNodeLine.getRuleNodeTo())
                    .ruleLimitType(RuleLimitTypeVO.valueOf(ruleTreeNodeLine.getRuleLimitType()))
                    .ruleLimitValue(RuleLogicCheckTypeVO.valueOf(ruleTreeNodeLine.getRuleLimitValue()))
                    .build();

            List<RuleTreeNodeLineVO> ruleTreeNodeLineVOList = ruleTreeNodeLineMap.computeIfAbsent(ruleTreeNodeLine.getRuleNodeFrom(), k -> new ArrayList<>());
            ruleTreeNodeLineVOList.add(ruleTreeNodeLineVO);
        }

        // 2. tree node 转换为Map结构
        Map<String, RuleTreeNodeVO> treeNodeMap = new HashMap<>();
        for (RuleTreeNode ruleTreeNode : ruleTreeNodes) {
            RuleTreeNodeVO ruleTreeNodeVO = RuleTreeNodeVO.builder()
                    .treeId(ruleTreeNode.getTreeId())
                    .ruleKey(ruleTreeNode.getRuleKey())
                    .ruleDesc(ruleTreeNode.getRuleDesc())
                    .ruleValue(ruleTreeNode.getRuleValue())
                    .treeNodeLineVOList(ruleTreeNodeLineMap.get(ruleTreeNode.getRuleKey()))
                    .build();
            treeNodeMap.put(ruleTreeNode.getRuleKey(), ruleTreeNodeVO);
        }

        // 3. 构建 Rule Tree
        RuleTreeVO ruleTreeVODB = RuleTreeVO.builder()
                .treeId(ruleTree.getTreeId())
                .treeName(ruleTree.getTreeName())
                .treeDesc(ruleTree.getTreeDesc())
                .treeRootRuleNode(ruleTree.getTreeRootRuleKey())
                .treeNodeMap(treeNodeMap)
                .build();

        log.info("ruleTreeVODB: {}" , ruleTreeVODB.toString());

        // 缓存里没有，去set Value值
        redisService.setValue(cacheKey, ruleTreeVODB);


        return ruleTreeVODB;
    }

    @Override
    public void cacheStrategyAwardCount(String cacheKey, Integer awardCount) {
        // 从Redis中获取key为cacheKey的库存
        Long cacheAwardCount = redisService.getAtomicLong(cacheKey);
        // 不存在这个库存，不执行后续操作
        if(null == cacheAwardCount) return;
        // 更新缓存值
        redisService.setAtomicLong(cacheKey, awardCount);
    }

    @Override
    public Boolean subtractionAwardStock(String cacheKey) {
        // 调用 Redis 的 DECR 命令，将 cacheKey 的值原子性地减 1，并返回扣减后的剩余值
        long surplus = redisService.decr(cacheKey); // 返回剩余库存值
        if(surplus < 0) {
            redisService.setValue(cacheKey, 0); // 设定为空
            return false;
        }
        /** 兜底操作，加一个锁*/
        /**
         * 加锁目的
         *  防止库存恢复后重复扣例如：库存从 99 被错误恢复到 100，另一个请求可能重复扣减）。
         *  确保同一剩余库存值只能被一个线程处理后续逻辑（例如：库存告警、异步补偿）。
         */
        String lockKey = cacheKey + Constants.UNDERLINE + surplus;
        Boolean lock = redisService.setNx(lockKey);
        if(!lock){
            log.info("策略奖品库存加锁失败 {}", lockKey);
        }
        return lock;
    }

    /**
     * 场景：订单超时未支付库存回退
     * （1）用户下单：用户抢到奖品后，先扣减库存（预占库存），但需要在3s内完成支付
     * （2）延迟队列作用：
     * 将预占库存的任务放入延迟队列，3s后触发检查
     * 若用户未在3s内支付，下游服务从队列中取出任务，回滚库存
     * 若用户已支付，任务可被忽略或标记为已完成
     *
     * 可用于分布式协作：多台服务器同时监听同一队列，实现负载均衡和高可用
     *
     * @param strategyAwardStockKeyVO
     */
    @Override
    public void awardStockConsumeSendQueue(StrategyAwardStockKeyVO strategyAwardStockKeyVO) {
        // cacheKey：Redis 中用于标识延迟队列的键
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUERY_KEY;
        // 获取阻塞队列： Redission提供的分布式阻塞队列，支持多线程/多服务协同消费队列中的消息
        RBlockingQueue<StrategyAwardStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);
        // 获取延迟队列  允许消息在指定延迟时间后进入阻塞队列
        // 消息不会立即被消费，而是在延迟时间（如3秒）后才会被下游获取。
        // 底层依赖 Redis 的 zset（有序集合）实现，按到期时间排序。
        RDelayedQueue<StrategyAwardStockKeyVO> delayedQueue = redisService.getDelayedQueue(blockingQueue);
        // 加入到延迟队列里（让我的下游处理有一个延迟的时间）
        delayedQueue.offer(strategyAwardStockKeyVO, 3, TimeUnit.SECONDS);
    }

    /**
     * 获取队列值
     * @return
     */
    @Override
    public StrategyAwardStockKeyVO takeQueueValue() {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUERY_KEY;
        RBlockingQueue<StrategyAwardStockKeyVO> destinationQueue = redisService.getBlockingQueue(cacheKey);
        return destinationQueue.poll();// 弹出
    }

    @Override
    public void updateStrategyAwardStock(Long strategyId, Integer awardId) {
        StrategyAward strategyAward = new StrategyAward();
        strategyAward.setStrategyId(strategyId);
        strategyAward.setAwardId(awardId);
        strategyAwardDao.updateStrategyAwardStock(strategyAward);
    }

    @Override
    public StrategyAwardEntity queryStrategyAwardEntity(Long strategyId, Integer awardId) {
        // 数据库查询我们要的信息
        // 优先从缓存获取
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_KEY + strategyId + Constants.UNDERLINE + awardId;
        StrategyAwardEntity strategyAwardEntity = redisService.getValue(cacheKey);
        if(null != strategyAwardEntity) return strategyAwardEntity;

        // 查询数据
        StrategyAward strategyAwardReq = new StrategyAward();
        strategyAwardReq.setStrategyId(strategyId);
        strategyAwardReq.setAwardId(awardId);
        StrategyAward strategyAwardRes = strategyAwardDao.queryStrategyAward(strategyAwardReq);

        strategyAwardEntity = StrategyAwardEntity.builder()
                .strategyId(strategyAwardRes.getStrategyId())
                .awardId(strategyAwardRes.getAwardId())
                .awardCount(strategyAwardRes.getAwardCount())
                .awardCountSurplus(strategyAwardRes.getAwardCountSurplus())
                .awardRate(strategyAwardRes.getAwardRate())
                .sort(strategyAwardRes.getSort())
                .awardTitle(strategyAwardRes.getAwardTitle())
                .awardSubtitle(strategyAwardRes.getAwardSubtitle())
                .build();
        // 把结果写回缓存
        redisService.setValue(cacheKey, strategyAwardEntity);
        return strategyAwardEntity;
    }


}
